package com.ifly.voice.recognizer;

import java.io.File;
import java.util.concurrent.LinkedBlockingQueue;

import com.ifly.voice.app.App;
import com.ifly.voice.app.Logs;
import com.lidroid.xutils.util.LogUtils;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import edu.cmu.pocketsphinx.Config;
import edu.cmu.pocketsphinx.Decoder;
import edu.cmu.pocketsphinx.Hypothesis;
import edu.cmu.pocketsphinx.pocketsphinx;

/**
 * Speech recognition task, which runs in a worker thread.
 * 
 * This class implements speech recognition for this demo application. It takes
 * the form of a long-running task which accepts requests to start and stop
 * listening, and emits recognition results to a listener.
 * 
 * @author David Huggins-Daines <dhuggins@cs.cmu.edu>
 */
public class RecognizerTask implements Runnable
{
	/**
	 * Audio recording task.
	 * 
	 * This class implements a task which pulls blocks of audio from the system
	 * audio input and places them on a queue.
	 * 
	 * @author David Huggins-Daines <dhuggins@cs.cmu.edu>
	 */
	class AudioTask implements Runnable
	{
		/**
		 * Queue on which audio blocks are placed.
		 */
		LinkedBlockingQueue<short[]> q;
		AudioRecord rec;
		int block_size;
		boolean done;

		static final int DEFAULT_BLOCK_SIZE = 512;

		AudioTask()
		{
			this.init(new LinkedBlockingQueue<short[]>(), DEFAULT_BLOCK_SIZE);
		}

		AudioTask(LinkedBlockingQueue<short[]> q)
		{
			this.init(q, DEFAULT_BLOCK_SIZE);
		}

		AudioTask(LinkedBlockingQueue<short[]> q, int block_size)
		{
			this.init(q, block_size);
		}

		void init(LinkedBlockingQueue<short[]> q, int block_size)
		{
			this.done = false;
			this.q = q;
			this.block_size = block_size;
			this.rec = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, 8000, AudioFormat.CHANNEL_IN_MONO,
					AudioFormat.ENCODING_PCM_16BIT, 8192);
		}

		public int getBlockSize()
		{
			return block_size;
		}

		public void setBlockSize(int block_size)
		{
			this.block_size = block_size;
		}

		public LinkedBlockingQueue<short[]> getQueue()
		{
			return q;
		}

		public void stop()
		{
			this.done = true;
		}

		public void run()
		{
			this.rec.startRecording();
			while (!this.done)
			{
				int nshorts = this.readBlock();
				if (nshorts <= 0)
					break;
			}
			this.rec.stop();
			this.rec.release();
		}

		int readBlock()
		{
			short[] buf = new short[this.block_size];
			int nshorts = this.rec.read(buf, 0, buf.length);
			if (nshorts > 0)
			{
				Log.d(getClass().getName(), "Posting " + nshorts + " samples to queue");
				this.q.add(buf);
			}
			return nshorts;
		}
	}

	/**
	 * PocketSphinx native decoder object.
	 */
	Decoder ps;
	/**
	 * Audio recording task.
	 */
	AudioTask audio;
	/**
	 * Thread associated with recording task.
	 */
	Thread audio_thread;
	/**
	 * Queue of audio buffers.
	 */
	LinkedBlockingQueue<short[]> audioq;
	/**
	 * Listener for recognition results.
	 */
	RecognitionListener rl;
	/**
	 * Whether to report partial results.
	 */
	boolean use_partials;

	/**
	 * State of the main loop.
	 */
	enum State
	{
		IDLE, LISTENING
	};

	/**
	 * Events for main loop.
	 */
	enum Event
	{
		NONE, START, STOP, SHUTDOWN
	};

	/**
	 * Current event.
	 */
	Event mailbox;

	public RecognitionListener getRecognitionListener()
	{
		return rl;
	}

	public void setRecognitionListener(RecognitionListener rl)
	{
		this.rl = rl;
	}

	public void setUsePartials(boolean use_partials)
	{
		this.use_partials = use_partials;
	}

	public boolean getUsePartials()
	{
		return this.use_partials;
	}

	String modul = "/3377";
	private Config c;

	public RecognizerTask()
	{
		String targetFile = App.getInstance().getDicHelp().getTargetFile();
		pocketsphinx.setLogfile(targetFile + "/pocketsphinx.log");
		Logs.d(targetFile + "/pocketsphinx.log");
		Logs.d(targetFile);

		boolean exists = new File(targetFile + "/pocketsphinx.log").exists();
		boolean exists2 = new File(targetFile).exists();
		Logs.d(exists);
		Logs.d(exists2);
		c = new Config();

		Logs.d("TargetFile=" + targetFile + "/tdt_sc_8k");
		Logs.d("TargetFile=" + targetFile + modul + ".dic");
		Logs.d("TargetFile=" + targetFile + modul + ".lm");
		c.setString("-hmm", targetFile + "/tdt_sc_8k");
		c.setString("-dict", targetFile + modul + ".dic");
		c.setString("-lm", targetFile + modul + ".lm");
		c.setFloat("-samprate", 8000.0);
		c.setInt("-maxhmmpf", 2000);
		c.setInt("-maxwpf", 10);
		c.setInt("-pl_window", 2);
		c.setBoolean("-backtrace", true);
		c.setBoolean("-bestpath", false);

		this.audioq = new LinkedBlockingQueue<short[]>();
		this.use_partials = false;
		this.mailbox = Event.NONE;
	}

	public void run()
	{
		/* Main loop for this thread. */
		boolean done = false;
		/* State of the main loop. */
		State state = State.IDLE;
		/* Previous partial hypothesis. */
		String partial_hyp = null;

		while (!done)
		{
			/* Read the mail. */
			Event todo = Event.NONE;
			synchronized (this.mailbox)
			{
				todo = this.mailbox;
				/* If we're idle then wait for something to happen. */
				if (state == State.IDLE && todo == Event.NONE)
				{
					try
					{
						Log.d(getClass().getName(), "waiting");
						this.mailbox.wait();
						todo = this.mailbox;
						Log.d(getClass().getName(), "got" + todo);
					}
					catch (InterruptedException e)
					{
						/* Quit main loop. */
						Logs.d("Interrupted waiting for mailbox, shutting down");
						todo = Event.SHUTDOWN;
					}
				}
				this.mailbox = Event.NONE;
			}
			switch (todo)
			{
			case NONE:
				if (state == State.IDLE)
					Logs.d("Received NONE in mailbox when IDLE, threading error?");
				break;
			case START:
				if (state == State.IDLE)
				{
					Log.d(getClass().getName(), "START");
					this.ps = new Decoder(c);
					this.audio = new AudioTask(this.audioq, 1024);
					this.audio_thread = new Thread(this.audio);
					this.ps.startUtt();
					this.audio_thread.start();
					state = State.LISTENING;
				}
				else
					Log.e(getClass().getName(), "Received START in mailbox when LISTENING");
				break;
			case STOP:

				if (state == State.IDLE)
					Log.e(getClass().getName(), "Received STOP in mailbox when IDLE");
				else
				{
					Log.d(getClass().getName(), "STOP");
					assert this.audio != null;
					this.audio.stop();
					try
					{
						this.audio_thread.join();
					}
					catch (InterruptedException e)
					{
						Log.e(getClass().getName(), "Interrupted waiting for audio thread, shutting down");
						done = true;
					}
					/* Drain the audio queue. */
					short[] buf;
					while ((buf = this.audioq.poll()) != null)
					{
						Log.d(getClass().getName(), "Reading " + buf.length + " samples from queue");
						this.ps.processRaw(buf, buf.length, false, false);
					}
					this.ps.endUtt();
					this.audio = null;
					this.audio_thread = null;

					Hypothesis hyp = this.ps.getHyp();
					if (this.rl != null)
					{
						if (hyp == null)
						{
							Log.d(getClass().getName(), "Recognition failure");
							this.rl.onError(-1);
						}
						else
						{
							Bundle b = new Bundle();
							Log.e(getClass().getName(), "zs log Final hypothesis: " + hyp.getHypstr());
							Log.e(getClass().getName(), ps.getUttid());
							b.putString("hyp", hyp.getHypstr());
							this.rl.onResults(b);

						}
					}
					state = State.IDLE;
				}
				break;
			case SHUTDOWN:
				Log.d(getClass().getName(), "SHUTDOWN");
				if (this.audio != null)
				{
					this.audio.stop();
					assert this.audio_thread != null;
					try
					{
						this.audio_thread.join();
					}
					catch (InterruptedException e)
					{
						/* We don't care! */
					}
				}
				this.ps.endUtt();
				this.audio = null;
				this.audio_thread = null;
				state = State.IDLE;
				done = true;
				break;
			}
			/*
			 * Do whatever's appropriate for the current state. Actually this
			 * just means processing audio if possible.
			 */
			if (state == State.LISTENING)
			{
				assert this.audio != null;
				try
				{
					short[] buf = this.audioq.take();
					Log.d(getClass().getName(), "Reading " + buf.length + " samples from queue");
					this.ps.processRaw(buf, buf.length, false, false);
					Hypothesis hyp = this.ps.getHyp();
					if (hyp != null)
					{
						String hypstr = hyp.getHypstr();
						if (hypstr != partial_hyp)
						{
							Log.d(getClass().getName(), "Hypothesis: " + hyp.getHypstr() + " Uttid: " + hyp.getUttid()
									+ " Best_score: " + hyp.getBest_score());
							if (this.rl != null && hyp != null)
							{
								String hypstr2 = hyp.getHypstr();
								if (hypstr2 != null)
								{
									Bundle b = new Bundle();
									b.putString("hyp", hyp.getHypstr());
									this.rl.onPartialResults(b);
								}
								ps.endUtt();
								ps.startUtt();
							}
						}
						partial_hyp = hypstr;
					}
				}
				catch (InterruptedException e)
				{
					Log.d(getClass().getName(), "Interrupted in audioq.take");
				}
			}
		}
	}

	public void start()
	{
		Log.d(getClass().getName(), "signalling START");
		synchronized (this.mailbox)
		{
			this.mailbox.notifyAll();
			Log.d(getClass().getName(), "signalled START");
			this.mailbox = Event.START;
		}
	}

	public void stop()
	{
		Log.d(getClass().getName(), "signalling STOP");
		synchronized (this.mailbox)
		{
			this.mailbox.notifyAll();
			Log.d(getClass().getName(), "signalled STOP");
			this.mailbox = Event.STOP;
		}
	}

	public void shutdown()
	{
		Log.d(getClass().getName(), "signalling SHUTDOWN");
		synchronized (this.mailbox)
		{
			this.mailbox.notifyAll();
			Log.d(getClass().getName(), "signalled SHUTDOWN");
			this.mailbox = Event.SHUTDOWN;
		}
	}
}